/***************************************************************
*
* (c) 2008 Markus Dittrich 
*
* This program is free software; you can redistribute it 
* and/or modify it under the terms of the GNU General Public 
* License Version 3 as published by the Free Software Foundation. 
* either version 3 of the License, or (at your option) any 
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License Version 3 for more details.
*
* You should have received a copy of the GNU General Public 
* License along with this program; if not, write to the Free 
* Software Foundation, Inc., 59 Temple Place - Suite 330, 
* Boston, MA 02111-1307, USA.
*
****************************************************************/

/* stdlib includes */
#include <float.h>

/* STL includes */
#include <cmath>
#include <iomanip>
#include <limits>
#include <iostream>

/* boost includes */
#include <boost/assign/std/vector.hpp>

/* Qt includes */
#include <QColor>
#include <QKeyEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QPen>
#include <QPrinter>
#include <QTextOption>
#include <QTextDocument>
#include <QWheelEvent>

/* local includes */
#include "basic_definitions.h"
#include "graph_canvas.h"
#include "settings.h"


/* import a few names into namespace */
using std::map;
using std::vector;
using namespace boost::assign;


//--------------------------------------------------------------
// constructor
//--------------------------------------------------------------
GraphCanvas::GraphCanvas(MainWindow* theParent, int xOrigin,
  int yOrigin, int aWidth, int aHeight, const DataMap& theData,
  const PlotPropertiesMap& theplotProperties,
  const vector<QString>& activeData, DataLabelManager& theLabels,
  const Settings& settings)
  :
    QWidget(theParent),
    parent_(theParent),
    canvasOriginX_(xOrigin),
    canvasOriginY_(yOrigin),
    canvasWidth_(aWidth),
    canvasHeight_(aHeight),
    xHalfTickLength_(3),
    yHalfTickLength_(3),
    tickPenWidth_(1),
    axesPenWidth_(1),
    labelFontSize_(8),
    rubberBandOn_(false),
    canvasBackgroundColor_(QColor(Qt::white)),
    data_(theData),
    plotProperties_(theplotProperties),
    activeData_(activeData),
    labels_(theLabels),
    settings_(settings)
{
  status_ = SUCCESSFULLY_CONSTRUCTED;
}


//---------------------------------------------------------------
// main init function 
//---------------------------------------------------------------
bool GraphCanvas::Init()
{
  if ( status_ != SUCCESSFULLY_CONSTRUCTED )
  {
    return false;
  }

  /* general setup */
  setBackgroundRole(QPalette::Dark);
  setAutoFillBackground(true);
  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  setFocusPolicy(Qt::StrongFocus);

  set_axes_dimensions_();
  initialize_label_layout_();
  generate_popup_window_();
  setup_popUp_search_mask_();

  /* generate canvas map */
  canvasMap_ = CanvasMapPtr(
      new CanvasToDataMap(canvasWidth_, canvasHeight_));

  /* initialize object managing the dimensions of the plotted
   * data */
  canvasLayout_.initialize(this);

  /* enable mouse tracking on this widget and connect movements
   * to our parent */
  setMouseTracking(true);
  connect(this, SIGNAL(mouse_moved(double, double)), parent_,
      SLOT(update_mouse_position(double, double)));

  /* connect zooming/panning events to CanvasLayoutManager */
  connect(this, SIGNAL(zoom_event(int)), &canvasLayout_,
      SLOT(zoom(int)));
  connect(this, SIGNAL(pan_x_event(int)), &canvasLayout_,
      SLOT(pan_x(int)));
  connect(this, SIGNAL(pan_y_event(int)), &canvasLayout_,
      SLOT(pan_y(int)));
  connect(this, SIGNAL(reset_canvas_view_event()), &canvasLayout_,
      SLOT(reset()));
  connect(this, SIGNAL(focus_region_event(double,double,double,
   double)), &canvasLayout_,SLOT(focus_region(double,double,
   double,double)));

  /* done - create the pixmap */
  update_pixmap();

  return true;
}


/*****************************************************************
 *
 *  public member functions
 *
 ****************************************************************/

//-----------------------------------------------------------------
// toggles the ScalingDirty_ flag to on and the scaling be
// recomputed during the next Canvas update
//-----------------------------------------------------------------
void GraphCanvas::mark_scaling_dirty()
{
  canvasLayout_.set_dirty();
}


//---------------------------------------------------------------
// prints the content of the canvas to the provided QPrinter
//---------------------------------------------------------------
void GraphCanvas::print(QPrinter& aPrinter) const
{
  QPainter painter(&aPrinter);
  painter.drawPixmap(0,0,pixMap_);
}


//----------------------------------------------------------------
// exports the content of the canvas to the provided filename
// in the requested format
//----------------------------------------------------------------
void GraphCanvas::export_bitmap(
    QString& filename, QString& format) const
{
  pixMap_.save(filename,format.toLatin1());
}


/*****************************************************************
 *
 * public slots 
 *
 *****************************************************************/

//---------------------------------------------------------------
// updates the main pixmap used to draw the complete canvas
//---------------------------------------------------------------
void GraphCanvas::update_pixmap()
{
  pixMap_ = QPixmap(QSize(canvasWidth_,canvasHeight_));
  pixMap_.fill(this, 0, 0);

  QPainter painter(&pixMap_);
  painter.initFrom(this);

  /* paint on pixmax */
  paint_background_(painter);
  draw_axes_(painter);
  draw_labels_(painter);

  if ( !data_.empty() )
  {
    canvasLayout_.update();
    draw_data_(painter);
    draw_ticks_and_labels_(painter);
  }

  /* copy bitmap to widget */
  update();
}


//----------------------------------------------------------------
// handles zooming into the data viewed on canvas
//----------------------------------------------------------------
void GraphCanvas::zoom_in()
{
  emit zoom_event(1); 
  update_pixmap();
}


//---------------------------------------------------------------
// handles zooming out of the data viewed on canvas
//---------------------------------------------------------------
void GraphCanvas::zoom_out()
{
  emit zoom_event(-1);
  update_pixmap();
}


//---------------------------------------------------------------
// handles panning left on data viewed on canvas
//---------------------------------------------------------------
void GraphCanvas::pan_left()
{
  emit pan_x_event(-1);
  update_pixmap();
}


//---------------------------------------------------------------
// handles panning right on data viewed on canvas
//---------------------------------------------------------------
void GraphCanvas::pan_right()
{
  emit pan_x_event(1);
  update_pixmap();
}

//---------------------------------------------------------------
// handles panning up on data viewed on canvas
//---------------------------------------------------------------
void GraphCanvas::pan_up()
{
  emit pan_y_event(-1);
  update_pixmap();
}

//---------------------------------------------------------------
// handles panning down on data viewed on canvas
//---------------------------------------------------------------
void GraphCanvas::pan_down()
{
  emit pan_y_event(1);
  update_pixmap();
}


//---------------------------------------------------------------
// resets canvas view to the default
//---------------------------------------------------------------
void GraphCanvas::reset_view()
{
  emit reset_canvas_view_event();
  update_pixmap();
}



/*****************************************************************
 *
 *  protected member functions
 *
 ****************************************************************/

//--------------------------------------------------------------
// repaint the Canvas; simply copy pixMap_ onto the widget
//--------------------------------------------------------------
void GraphCanvas::paintEvent(QPaintEvent* aPaintEvent)
{
  aPaintEvent->accept();
  QPainter painter(this);
  painter.drawPixmap(0,0,pixMap_);

  /* show rubber band if present */
  if ( rubberBandOn_ )
  {
    painter.setPen(palette().dark().color());
    painter.drawRect(rubberBand_.normalized().adjusted(0,0,-1,-1));
  }
}


//-------------------------------------------------------------
// re-adjust the canvas size on resizing
//-------------------------------------------------------------
void GraphCanvas::resizeEvent(QResizeEvent* aResizeEvent)
{
  aResizeEvent->accept();

  QSize currentSize = frameSize();
  canvasWidth_ = currentSize.width();
  canvasHeight_ = currentSize.height();
  update_pixmap();
}


//----------------------------------------------------------------
// overload mouse double click events 
//----------------------------------------------------------------
void GraphCanvas::mouseDoubleClickEvent(QMouseEvent* anEvent)
{
  anEvent->accept();
}


//----------------------------------------------------------------
// overload mouse press events
//----------------------------------------------------------------
void GraphCanvas::mousePressEvent(QMouseEvent* anEvent)
{
  anEvent->accept();

  /* Show popup info on right click if we're on top of a 
   * data point, and close existing ones otherwise. */
  if ( anEvent->button() == Qt::RightButton )
  {
    show_popup_window_(anEvent);
  }

  /* left click starts a new rubber band and removes any data
   * popup windows if present */
  if ( anEvent->button() == Qt::LeftButton )
  {
    popUp_->setVisible(false);

    rubberBandOn_ = true;
    rubberBand_.setTopLeft(anEvent->pos());
    rubberBand_.setBottomRight(anEvent->pos());
    repaint_RubberBand_region_();
    setCursor(Qt::CrossCursor);
  }
}


//----------------------------------------------------------------
// overload mouse release events
//----------------------------------------------------------------
void GraphCanvas::mouseReleaseEvent(QMouseEvent* anEvent)
{
  anEvent->accept();

  /* if rubber band is on we need to take the proper zoom and
   * and pan action */
  if ( rubberBandOn_ && anEvent->button() == Qt::LeftButton )
  {
    rubberBandOn_ = false;
    repaint_RubberBand_region_();
    unsetCursor();

    /* determine size and location of selected area */
    QRect aRect = rubberBand_.normalized();

    /* if the width of the rectangle is smaller than 2 pixels
     * we assume the user accidentally pressed the left mouse
     * button and don't do anything */
    if ( aRect.width() <= 2 )
    {
      return;
    }

    /* convert canvas positions into data positions */
    double xmin = get_canvas_x_data_(aRect.left());
    double xmax = get_canvas_x_data_(aRect.left() + aRect.width());
    double ymin = get_canvas_y_data_(aRect.bottom());
    double ymax = get_canvas_y_data_(aRect.bottom() - aRect.height());

    /* focus on region */
    emit focus_region_event(xmin, xmax, ymin, ymax);
    update_pixmap();
  }
}


//----------------------------------------------------------------
// overload mouse wheel events; 
// wheel up events zoom in, wheel down events zoom out
//----------------------------------------------------------------
void GraphCanvas::wheelEvent(QWheelEvent* anEvent)
{
  anEvent->accept();
  int motion = anEvent->delta();
  if ( motion > 0 )
  {
    zoom_in(); 
  }
  else if ( motion < 0 )
  {
    zoom_out();
  }
}


//----------------------------------------------------------------
// overload mouse move events
//----------------------------------------------------------------
void GraphCanvas::mouseMoveEvent(QMouseEvent* anEvent)
{
  anEvent->accept();

  QPoint canvasPosition = anEvent->pos();
  int xPos = canvasPosition.x();
  int yPos = canvasPosition.y();

  /* generate mouse event for MainWindow's mouse position view */
  double xDataValue = get_canvas_x_data_(xPos);
  double yDataValue = get_canvas_y_data_(yPos);
  emit mouse_moved(xDataValue, yDataValue);

  /* check if a detailed data info popup was request */
  if ( settings_.get_continuous_data_info() )
  {
    show_popup_window_(anEvent);
  } 

  /* adjust the rubber band size if there is one */
  if ( rubberBandOn_ )
  {
    repaint_RubberBand_region_();
    rubberBand_.setBottomRight(anEvent->pos());
    repaint_RubberBand_region_();
  }
}


//----------------------------------------------------------------
// overload key press events
//----------------------------------------------------------------
void GraphCanvas::keyPressEvent(QKeyEvent* anEvent)
{
  anEvent->accept();

  switch(anEvent->key())
  {
    case Qt::Key_Plus:
      zoom_in();
      break;
    case Qt::Key_Minus:
      zoom_out();
      break;
    case Qt::Key_Left:
      pan_left();
      break;
    case Qt::Key_Right:
      pan_right();
      break;
    case Qt::Key_Up:
      pan_up();
      break;
    case Qt::Key_Down:
      pan_down();
      break;
    case Qt::Key_Escape:
      reset_view();
      break;
    default:
      QWidget::keyPressEvent(anEvent);
  }
}



/*****************************************************************
 *
 * private member functions
 *
 *****************************************************************/

//----------------------------------------------------------------
// repaints the regions that were previously hidden by the
// rubber band
//----------------------------------------------------------------
void GraphCanvas::repaint_RubberBand_region_()
{
  QRect repaintRect = rubberBand_.normalized();
  update(repaintRect.left(), repaintRect.top(), 
      repaintRect.width(), 1);
  update(repaintRect.left(), repaintRect.top(), 
      1, repaintRect.height());
  update(repaintRect.left(), repaintRect.bottom(), 
      repaintRect.width(), 1);
  update(repaintRect.right(), repaintRect.top(), 
      1, repaintRect.height());
}


//---------------------------------------------------------------
// check if a particular data set is marked as hidden
//---------------------------------------------------------------
bool GraphCanvas::is_hidden_(const QString& dataName) const
{
  const PlotPropertiesPtr currentProperties = 
    retrieve_plot_properties_(dataName);
  return currentProperties->is_hidden();
}


//--------------------------------------------------------------
// generate the data info popup window
//--------------------------------------------------------------
void GraphCanvas::generate_popup_window_()
{
  popUp_ = new PopUpInfoWidget(this, Qt::ToolTip);
}


//--------------------------------------------------------------
// sets the axis dimensions based on the canvas size
//--------------------------------------------------------------
void GraphCanvas::set_axes_dimensions_()
{
  axesOriginX_ = canvasOriginX_ + AXESOFFSETX_;
  axesOriginY_ = canvasOriginY_ + canvasHeight_ - AXESOFFSETY_;
  axesWidth_ = canvasWidth_ - (2 * AXESOFFSETX_);
  axesHeight_ = canvasHeight_ - (2 * AXESOFFSETY_);
}



//--------------------------------------------------------------
// setup arrays holding the pixel masks for searching the 
// neighborhood
//--------------------------------------------------------------
void GraphCanvas::setup_popUp_search_mask_()
{
  /* level 0 */
  vector<QPoint> level0;
  level0 += QPoint(0,0); 
  popUpSearchMask_.push_back(level0);

  /* level 1 */
  vector<QPoint> level1;
  level1 += QPoint(0,0),QPoint(1,0),QPoint(-1,0),QPoint(0,1),
            QPoint(0,-1),QPoint(1,1),QPoint(1,-1),QPoint(-1,1),
            QPoint(-1,-1);
  popUpSearchMask_.push_back(level1);

  /* level 2 */
  vector<QPoint> level2;
  level2 += QPoint(0,0),QPoint(1,0),QPoint(-1,0),QPoint(0,1),
            QPoint(0,-1),QPoint(1,1),QPoint(1,-1),QPoint(-1,1),
            QPoint(-1,-1),QPoint(2,0),QPoint(2,1),QPoint(2,2),
            QPoint(2,-1),QPoint(2,-2),QPoint(-2,0),QPoint(-2,1),
            QPoint(-2,2),QPoint(-2,-1),QPoint(-2,-2),QPoint(-1,-2),
            QPoint(0,-2),QPoint(1,-2),QPoint(-1,2),QPoint(0,2),
            QPoint(1,2);
  popUpSearchMask_.push_back(level2);
}


//-------------------------------------------------------------
// initialize the default position and dimensions of the main,
// axis, and data labels
//-------------------------------------------------------------
void GraphCanvas::initialize_label_layout_()
{
  /* main label */
  labels_.set_label_pos(MAIN_LABEL, QPoint(axesOriginX_
        + static_cast<int>(axesWidth_/2.0), 5), true);

  /* x axis label */
  labels_.set_label_pos(X_AXIS_LABEL, QPoint(axesOriginX_
        + static_cast<int>(axesWidth_/2.0), axesOriginY_ + 30), true);

  /* y axis label */
  labels_.set_label_pos(Y_AXIS_LABEL, QPoint(axesOriginX_ - 80, 
        axesOriginY_ - static_cast<int>(axesHeight_/2.0)), true);

  /* data labels */
  labels_.set_label_pos(DATA_LABEL,
      QPoint(axesOriginX_ + static_cast<int>(axesWidth_/2.0) + 100,
        axesOriginY_ - static_cast<int>(axesHeight_/2.0)), true);
}


//-------------------------------------------------------------
// paint background of canvas in canvasBackgroundColor_
//-------------------------------------------------------------
void GraphCanvas::paint_background_(QPainter& currentPainter)
{
  currentPainter.fillRect
  (
    canvasOriginX_,
    canvasOriginY_,
    canvasWidth_,
    canvasHeight_,
    canvasBackgroundColor_
  );
}


//--------------------------------------------------------------
// paint axes onto the current canvas
//--------------------------------------------------------------
void GraphCanvas::draw_axes_(QPainter& currentPainter)
{
  QPen currentPen = get_axes_pen_();
  currentPainter.setPen(currentPen);

  /* draw the x-axis */
  currentPainter.drawLine
  (
    axesOriginX_,
    axesOriginY_,
    axesOriginX_ + axesWidth_,
    axesOriginY_
  );

  /* draw the y-axis */
  currentPainter.drawLine
  (
    axesOriginX_,
    axesOriginY_,
    axesOriginX_,
    axesOriginY_ - axesHeight_
  );
}


//---------------------------------------------------------------
// add the labels to the current canvas
//---------------------------------------------------------------
void GraphCanvas::draw_labels_(QPainter& currentPainter)
{
  /* text options for labels */
  QTextOption labelTextOption(Qt::AlignHCenter | Qt::AlignVCenter);
  labelTextOption.setWrapMode(QTextOption::WordWrap);

  /* use QTextDocument for labels */
  QTextDocument labelText;
  labelText.setDefaultTextOption (labelTextOption);

  /* main label */
  currentPainter.resetMatrix();
  currentPainter.translate(labels_.get_label_pos(MAIN_LABEL));
  labelText.setHtml(labels_.get_axis_label(MAIN_LABEL));
  labelText.drawContents(&currentPainter);

  /* x axis label */
  currentPainter.resetMatrix();
  currentPainter.translate(labels_.get_label_pos(X_AXIS_LABEL));
  labelText.setHtml(labels_.get_axis_label(X_AXIS_LABEL));
  labelText.drawContents(&currentPainter);

  /* y axis label */
  currentPainter.resetMatrix();
  currentPainter.translate(labels_.get_label_pos(Y_AXIS_LABEL));
  currentPainter.rotate(-90.0);
  labelText.setHtml(labels_.get_axis_label(Y_AXIS_LABEL));
  labelText.drawContents(&currentPainter);

  /* data labels */
  draw_data_labels_(currentPainter, labelText);

  /* reset the painter */
  currentPainter.resetMatrix();
}


//---------------------------------------------------------------
// add the data labels to the current canvas
//---------------------------------------------------------------
void GraphCanvas::draw_data_labels_(QPainter& currentPainter,
    QTextDocument& labelText)
{
  currentPainter.resetMatrix();
  currentPainter.translate(labels_.get_label_pos(DATA_LABEL));
  const map<QString,QString> dataLabels = labels_.get_data_labels();

  /* store current pen for later */
  QPen oldPen = currentPainter.pen();

  typedef std::pair<QString,QString> labelItem;
  FOREACH(const labelItem& aLabel, dataLabels)
  {
    /* check if there is a label */
    if ( !aLabel.second.isEmpty() )
    {
      /* retrieve and set active data color */
      QString dataName = aLabel.first;
      int lineWidth = get_line_width_(dataName);
      QColor lineColor = get_graph_color_(dataName);

      QPen currentPen;
      currentPen.setColor(lineColor);
      currentPen.setWidth(lineWidth);
      currentPainter.setPen(currentPen);

      /* NOTE: need to be more sophisticated for different 
       * font sizes */
      currentPainter.drawLine(-50,10,-10,10);
      labelText.setHtml(aLabel.second);
      labelText.drawContents(&currentPainter);
      currentPainter.translate(0,20);
    }
  }

  /* restore old pen */
  currentPainter.setPen(oldPen);
  
  /* reset the painter */
  currentPainter.resetMatrix();
}


//---------------------------------------------------------------
// convert a raw x coordinate into a proper x coordinate on the
// canvas
//---------------------------------------------------------------
inline int GraphCanvas::get_canvas_x_coordinate_(
    double xCoord) const
{
  return static_cast<int>(
      floor(axesOriginX_ + ( ( xCoord - canvasLayout_.xmin() ) 
          * canvasLayout_.xscaling() )));
}


//---------------------------------------------------------------
// convert a raw y coordinate into a proper y coordinate on the
// canvas
//---------------------------------------------------------------
inline int GraphCanvas::get_canvas_y_coordinate_(
    double yCoord) const
{
  return static_cast<int>(
      floor(axesOriginY_ - ( ( yCoord - canvasLayout_.ymin() ) 
          * canvasLayout_.yscaling() )));
}


//---------------------------------------------------------------
// convert x coordinate of a pixel on canvas into the 
// correspoding x data value based on the current canvas 
// scaling 
//---------------------------------------------------------------
inline double GraphCanvas::get_canvas_x_data_(
    int xPos) const
{
  int xPixelPos = xPos - axesOriginX_;
  return ((static_cast<double>(xPixelPos) / canvasLayout_.xscaling() ) 
      + canvasLayout_.xmin());
}


//---------------------------------------------------------------
// convert y coordinate of a pixel on canvas into the 
// correspoding y data value based on the current canvas 
// scaling 
//---------------------------------------------------------------
inline double GraphCanvas::get_canvas_y_data_(
    int yPos) const
{
  int yPixelPos = -(yPos - axesOriginY_);
  return ((static_cast<double>(yPixelPos) / canvasLayout_.yscaling() )
      + canvasLayout_.ymin());
}


//---------------------------------------------------------------
// compute the all data points to be plotted on the canvas
//---------------------------------------------------------------
void GraphCanvas::draw_data_(QPainter& currentPainter)
{
  /* clear canvas map */
  canvasMap_->clear();

  /* clip all but the are between the axes to plot the data */
  QRect clipper = get_canvas_data_clipper_();
  currentPainter.setClipRect(clipper);

  /* loop over all active data sets and plot them */
  FOREACH(QString dataName, activeData_)
  {
    /* do not plot hidden data sets */
    if ( is_hidden_(dataName) )
    {
      continue;
    }

    const DataObjectPtr currentData = retrieve_data_(dataName);
    const vector<double>& xData = currentData->get_x_data();
    const vector<double>& yData = currentData->get_y_data();

    QPoint* points;
    int dataLength;
    if ( has_element_count_as_x(currentData) && 
         has_element_count_as_y(currentData) )
    {
      dataLength = 1;
      points = new QPoint[1];
      points[0] = QPoint(0,0);
    }
    else if ( has_element_count_as_x(currentData) )
    {
      dataLength = yData.size();
      points = new QPoint[dataLength];
      for ( int counter=0; counter < dataLength; ++counter)
      {
        points[counter] = QPoint
          (
            get_canvas_x_coordinate_(counter),
            get_canvas_y_coordinate_(yData[counter])
          );
      }
    }
    else if ( has_element_count_as_y(currentData) )
    {
      dataLength = xData.size();
      points = new QPoint[dataLength];
      for ( int counter=0; counter < dataLength; ++counter)
      {
        points[counter] = QPoint
          (
            get_canvas_x_coordinate_(xData[counter]),
            get_canvas_y_coordinate_(counter)
          );
      }
    }
    else
    { 
      dataLength = yData.size();
      points = new QPoint[dataLength];
      for (int counter=0; counter < dataLength; ++counter)
      {
        points[counter] = QPoint
        (
          get_canvas_x_coordinate_(xData[counter]),
          get_canvas_y_coordinate_(yData[counter])
        );
      }
    }

    /* insert points into the canvas point cache */
    canvasMap_->insert(points,dataLength,dataName);

    /* finally, draw the graph */
    draw_individual_data_set_(currentPainter, dataName,
        points, dataLength);

    /* done with points */
    delete [] points;
  }

  /* remove the clipping restrinctions on painter */
  currentPainter.setClipping(false);
}


//----------------------------------------------------------------
// draw the graph for an individual data set based on the
// selected properties
//----------------------------------------------------------------
void GraphCanvas::draw_individual_data_set_(
    QPainter& currentPainter, QString& dataName, 
    QPoint* dataPoints, int dataLength)
{
  /* set up plot properties */
  QPen currentPen;
  QColor currentColor = get_graph_color_(dataName);
  currentPen.setColor(currentColor);
  int lineWidth = get_line_width_(dataName);
  currentPen.setWidth(lineWidth);
  currentPen.setJoinStyle(Qt::MiterJoin);
  currentPainter.setPen(currentPen);

  /* draw the graph in requested style */
  QString graphStyle = get_graph_style_(dataName);
  if ( graphStyle == "line" )
  {
    currentPainter.drawPolyline(dataPoints, dataLength);
  }
  else if ( graphStyle == "circles" )
  {
    int circleDiameter = get_circle_diameter_(dataName);
    for ( int counter = 0; counter < dataLength; ++counter)
    {
      /* since the ellipse is drawn inside a rectangle we need
       * to shift the rectangle's corner to make sure it's 
       * centered; Qt4.4's drawEllipse functions would fix this
       * but we don't want to be too bleeding edge */
      int shift = circleDiameter/2;
      currentPainter.drawEllipse
        (
         dataPoints[counter].x() - shift, 
         dataPoints[counter].y() - shift, 
         circleDiameter, 
         circleDiameter
        );
    }
  }
  else if ( graphStyle == "points" )
  {
    for ( int counter = 0; counter < dataLength; ++counter)
    {
      currentPainter.drawPoint(dataPoints[counter]);
    }
  }
}


//---------------------------------------------------------------
// draw the all ticks and their labels
//---------------------------------------------------------------
void GraphCanvas::draw_ticks_and_labels_(QPainter& currentPainter)
{
  draw_x_ticks_and_labels_(currentPainter);
  draw_y_ticks_and_labels_(currentPainter);
}


//---------------------------------------------------------------
// draw the x ticks and their labels
//---------------------------------------------------------------
void GraphCanvas::draw_x_ticks_and_labels_(QPainter& currentPainter)
{
  xTickLabels_.initialize(canvasLayout_.xmin(), canvasLayout_.xmax());
  vector<double> xTickValues = xTickLabels_.get_tick_values();
  vector<QString> xTickLabels = xTickLabels_.get_label_values();
  
  /* set up proper font and request tick labels */
  QPen currentPen = get_tick_pen_();
  currentPainter.setPen(currentPen);
  QFont currentFont;
  currentFont.setPointSize(labelFontSize_);
  currentPainter.setFont(currentFont);
  int labelLength = xTickLabels[0].length();

  int labelCounter = 0;
  FOREACH(double xTick, xTickValues)
  {
    int currentXPosition = get_canvas_x_coordinate_(xTick);

    currentPainter.drawLine
    (
      currentXPosition,
      axesOriginY_ + xHalfTickLength_,
      currentXPosition,
      axesOriginY_ - xHalfTickLength_  
    );

    QPoint labelPos
    (
      currentXPosition - labelLength/2 * labelFontSize_,
      axesOriginY_ + labelFontSize_ * xHalfTickLength_
    );

    currentPainter.drawText(labelPos, xTickLabels[labelCounter]);
    ++labelCounter;
  }
}


//---------------------------------------------------------------
// draw the y ticks and their labels
//---------------------------------------------------------------
void GraphCanvas::draw_y_ticks_and_labels_(QPainter& currentPainter)
{
  yTickLabels_.initialize(canvasLayout_.ymin(), canvasLayout_.ymax());
  vector<double> yTickValues = yTickLabels_.get_tick_values();
  vector<QString> yTickLabels = yTickLabels_.get_label_values();
 
  /* set up proper font and request tick labels */
  QPen currentPen = get_tick_pen_();
  currentPainter.setPen(currentPen);
  QFont currentFont;
  currentFont.setPointSize(labelFontSize_);
  currentPainter.setFont(currentFont);
  int labelLength = yTickLabels[0].length();

  int labelCounter = 0;
  FOREACH(double yTick, yTickValues)
  {
    int currentYPosition = get_canvas_y_coordinate_(yTick);

    currentPainter.drawLine
    (
      axesOriginX_ - yHalfTickLength_,
      currentYPosition,
      axesOriginX_ + yHalfTickLength_,
      currentYPosition
    );

    QPoint labelPos
    (
      axesOriginX_ - labelLength * labelFontSize_,
      currentYPosition + labelFontSize_/2
    );

    currentPainter.drawText(labelPos, yTickLabels[labelCounter]);
    ++labelCounter;
  }
}


//------------------------------------------------------------------
// assemble the proper QPen to draw the ticks based on the 
// currently selected tick properties
//------------------------------------------------------------------
QPen GraphCanvas::get_tick_pen_()
{
  QPen currentPen;
  currentPen.setWidth(tickPenWidth_);

  return currentPen;
}


//------------------------------------------------------------------
// assemble the proper QPen to draw the axes based on the
// currently selected tick properties
//------------------------------------------------------------------
QPen GraphCanvas::get_axes_pen_()
{
  QPen currentPen;
  currentPen.setWidth(axesPenWidth_);

  return currentPen;
}


//------------------------------------------------------------------
// returns a QRect with the clipping area on the current canvas
// used for plotting the data;
// We clip all but the area enclosed by the current axes plus a
// buffer region to make sure that "circle" plot style symbols are
// not cut off
//------------------------------------------------------------------
QRect GraphCanvas::get_canvas_data_clipper_()
{
  QRect clipper  
    (
     axesOriginX_, 
     axesOriginY_ - axesHeight_, 
     axesWidth_,
     axesHeight_
    );

  return clipper.adjusted(-4,-4,4,4);
}

  
//------------------------------------------------------------------
// helper function to retrieve plotPropertyPtr corresponding
// to dataName from container
//------------------------------------------------------------------
const PlotPropertiesPtr& GraphCanvas::retrieve_plot_properties_ 
  (QString dataName) const
{
  PlotPropertiesMap::const_iterator 
    currentPropertiesPos(plotProperties_.find(dataName));
  
  return (*currentPropertiesPos).second; 
}


//------------------------------------------------------------------
// retrieve plot color for selected data set
//------------------------------------------------------------------
const QColor& GraphCanvas::get_graph_color_(QString dataName) const
{
  const PlotPropertiesPtr currentProperties = 
    retrieve_plot_properties_(dataName);
  return currentProperties->get_color();
}


//-------------------------------------------------------------------
// retrieve plot style for selected data set
//-------------------------------------------------------------------
const QString& GraphCanvas::get_graph_style_(QString dataName) const
{
  const PlotPropertiesPtr currentProperties = 
    retrieve_plot_properties_(dataName);

  return currentProperties->get_penStyle();
}


//-------------------------------------------------------------------
// retrieve plot style for selected data set
//-------------------------------------------------------------------
int GraphCanvas::get_line_width_(QString dataName) const
{
  const PlotPropertiesPtr currentProperties = 
    retrieve_plot_properties_(dataName);

  return currentProperties->get_lineWidth();
}


//-------------------------------------------------------------------
// retrieve plot style for selected data set
//-------------------------------------------------------------------
int GraphCanvas::get_circle_diameter_(QString dataName) const
{
  const PlotPropertiesPtr currentProperties = 
    retrieve_plot_properties_(dataName);

  return currentProperties->get_circleDiameter();
}


//------------------------------------------------------------------
// retrieve pointer to data for requested data set 
//------------------------------------------------------------------
const DataObjectPtr GraphCanvas::retrieve_data_(
    const QString& dataName) const
{
  dataMapIterator dataLoc = data_.find(dataName);
  assert ( dataLoc != data_.end() );

  return (*dataLoc).second;
}


//------------------------------------------------------------------
// display popup info window if the current mouse position 
// corresponds to a valid data point
//------------------------------------------------------------------
void GraphCanvas::show_popup_window_(QMouseEvent* anEvent) const
{
  QPoint canvasPosition = anEvent->pos();
  int xPos = canvasPosition.x();
  int yPos = canvasPosition.y();

  /* make sure we are at a valid position */
  if (  ( xPos < 0 ||  xPos > canvasWidth_ ) 
     && ( yPos < 0 ||  yPos > canvasHeight_ ) )
  {
    return;
  }

  /* search all pixels inside the search mask; we display
   * the first one that we fine; 
   * FIXME: How should we handle the case of multiple data
   * points within the mask? */
  int maskSelector = settings_.get_canvas_data_selection_sensitivity();
  pixelMask currentMask = popUpSearchMask_[maskSelector];
  popUp_->setVisible(false);
  FOREACH(QPoint& point, currentMask)
  {
    const DataPoint& dataPoint = 
      canvasMap_->retrieve(xPos+point.x(),yPos+point.y());
  
    QString name = dataPoint.get<0>();
    if ( name != "" )
    {
      int index = dataPoint.get<1>();

      QPoint globalPosition = anEvent->globalPos();
      popUp_->move(globalPosition.x()+10,globalPosition.y()+10);
      update_popup_window_(name, index);
      popUp_->setVisible(true);
      break;
    }
  }
}


//----------------------------------------------------------------
// fill popup window with the information for the data point
// under the mouse pointer
//----------------------------------------------------------------
void GraphCanvas::update_popup_window_(const QString& dataName, 
    int row) const
{
  /* retrieve proper data set and row data */
  dataMapIterator dataLoc = data_.find(dataName);
  const DataObjectPtr currentData = (*dataLoc).second;
  vector<double> rowData = currentData->get_row_data(row);

  /* add current value of x data */
  double xLabel;
  int activeXColumn = currentData->get_active_x_column();
  if ( activeXColumn != ELEMENT_COUNT )
  {
    xLabel = rowData[activeXColumn];
  }
  else 
  {
    xLabel = row;
  }

  /* add current value of y data */
  double yLabel;
  int activeYColumn = currentData->get_active_y_column();
  if ( activeYColumn != ELEMENT_COUNT )
  {
    yLabel = rowData[activeYColumn];
  }
  else 
  {
    yLabel = row;
  }

  /* add any additional data present */
  vector<double> dataVector;
  for( int rowItem = 0; rowItem < rowData.size(); ++rowItem)
  {
    if ( (rowItem == activeXColumn) || (rowItem == activeYColumn) )
    {
      continue;
    }
    
    dataVector.push_back(rowData[rowItem]);
  }

  popUp_->set_content(dataName, xLabel, yLabel, dataVector);
}



